/* ----------------------------- dom/walker.js ------------------------------ */
(function() {
   // This function is to be called under a "walker" instance scope.
   function iterate( rtl, breakOnFalse ) {
      var range = this.range;

      // Return null if we have reached the end.
      if ( this._.end )
         return null;

      // This is the first call. Initialize it.
      if ( !this._.start ) {
         this._.start = 1;

         // A collapsed range must return null at first call.
         if ( range.collapsed ) {
            this.end();
            return null;
         }

         // Move outside of text node edges.
         range.optimize();
      }

      var node,
         startCt = range.startContainer,
         endCt = range.endContainer,
         startOffset = range.startOffset,
         endOffset = range.endOffset,
         guard,
         userGuard = this.guard,
         type = this.type,
         getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );

      // Create the LTR guard function, if necessary.
      if ( !rtl && !this._.guardLTR ) {
         // The node that stops walker from moving up.
         var limitLTR = endCt.type == VED.NODE_ELEMENT ? endCt : endCt.getParent();

         // The node that stops the walker from going to next.
         var blockerLTR = endCt.type == VED.NODE_ELEMENT ? endCt.getChild( endOffset ) : endCt.getNext();

         this._.guardLTR = function( node, movingOut ) {
            return ( ( !movingOut || !limitLTR.equals( node ) ) && ( !blockerLTR || !node.equals( blockerLTR ) ) && ( node.type != VED.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
         };
      }

      // Create the RTL guard function, if necessary.
      if ( rtl && !this._.guardRTL ) {
         // The node that stops walker from moving up.
         var limitRTL = startCt.type == VED.NODE_ELEMENT ? startCt : startCt.getParent();

         // The node that stops the walker from going to next.
         var blockerRTL = startCt.type == VED.NODE_ELEMENT ? startOffset ? startCt.getChild( startOffset - 1 ) : null : startCt.getPrevious();

         this._.guardRTL = function( node, movingOut ) {
            return ( ( !movingOut || !limitRTL.equals( node ) ) && ( !blockerRTL || !node.equals( blockerRTL ) ) && ( node.type != VED.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
         };
      }

      // Define which guard function to use.
      var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;

      // Make the user defined guard function participate in the process,
      // otherwise simply use the boundary guard.
      if ( userGuard ) {
         guard = function( node, movingOut ) {
            if ( stopGuard( node, movingOut ) === false )
               return false;

            return userGuard( node, movingOut );
         };
      } else
         guard = stopGuard;

      if ( this.current )
         node = this.current[ getSourceNodeFn ]( false, type, guard );
      else {
         // Get the first node to be returned.
         if ( rtl ) {
            node = endCt;

            if ( node.type == VED.NODE_ELEMENT ) {
               if ( endOffset > 0 )
                  node = node.getChild( endOffset - 1 );
               else
                  node = ( guard( node, true ) === false ) ? null : node.getPreviousSourceNode( true, type, guard );
            }
         } else {
            node = startCt;

            if ( node.type == VED.NODE_ELEMENT ) {
               if ( !( node = node.getChild( startOffset ) ) )
                  node = ( guard( startCt, true ) === false ) ? null : startCt.getNextSourceNode( true, type, guard );
            }
         }

         if ( node && guard( node ) === false )
            node = null;
      }

      while ( node && !this._.end ) {
         this.current = node;

         if ( !this.evaluator || this.evaluator( node ) !== false ) {
            if ( !breakOnFalse )
               return node;
         } else if ( breakOnFalse && this.evaluator )
            return false;

         node = node[ getSourceNodeFn ]( false, type, guard );
      }

      this.end();
      return this.current = null;
   }

   function iterateToLast( rtl ) {
      var node,
         last = null;

      while ( ( node = iterate.call( this, rtl ) ) )
         last = node;

      return last;
   }

   VED.dom.walker = new Class({
      
      initialize: function( range ) {
         this.range = range;
         this._ = {};
      },

      end: function() {
         this._.end = 1;
      },

      next: function() {
         return iterate.call( this );
      },

      previous: function() {
         return iterate.call( this, 1 );
      },

      checkForward: function() {
         return iterate.call( this, 0, 1 ) !== false;
      },

      checkBackward: function() {
         return iterate.call( this, 1, 1 ) !== false;
      },

      lastForward: function() {
         return iterateToLast.call( this );
      },

      lastBackward: function() {
         return iterateToLast.call( this, 1 );
      },

      reset: function() {
         delete this.current;
         this._ = {};
      }

   });

   // Anything whose display computed style is block, list-item, table,
   // table-row-group, table-header-group, table-footer-group, table-row,
   // table-column-group, table-column, table-cell, table-caption, or whose node
   // name is hr, br (when enterMode is br only) is a block boundary.
   var blockBoundaryDisplayMatch = { block:1,'list-item':1,table:1,'table-row-group':1,'table-header-group':1,'table-footer-group':1,'table-row':1,'table-column-group':1,'table-column':1,'table-cell':1,'table-caption':1 };

   VED.dom.element.prototype.isBlockBoundary = function( customNodeNames ) {
      var nodeNameMatches = customNodeNames ? VED.tools.extend( {}, VED.dtd.$block, customNodeNames || {} ) : VED.dtd.$block;

      // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)
      return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] || nodeNameMatches[ this.getName() ];
   };

   VED.dom.walker.blockBoundary = function( customNodeNames ) {
      return function( node, type ) {
         return !( node.type == VED.NODE_ELEMENT && node.isBlockBoundary( customNodeNames ) );
      };
   };

   VED.dom.walker.listItemBoundary = function() {
      return this.blockBoundary( { br:1 } );
   };

   VED.dom.walker.bookmark = function( contentOnly, isReject ) {
      function isBookmarkNode( node ) {
         return ( node && node.getName && node.getName() == 'span' && node.data( 'cke-bookmark' ) );
      }

      return function( node ) {
         var isBookmark, parent;
         // Is bookmark inner text node?
         isBookmark = ( node && node.type != VED.NODE_ELEMENT && ( parent = node.getParent() ) && isBookmarkNode( parent ) );
         // Is bookmark node?
         isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
         return !!( isReject ^ isBookmark );
      };
   };

   VED.dom.walker.whitespaces = function( isReject ) {
      return function( node ) {
         var isWhitespace;
         if ( node && node.type == VED.NODE_TEXT ) {
            // whitespace, as well as the text cursor filler node we used in Webkit. (#9384)
            isWhitespace = !node.getText().trim() ||
               VED.env.webkit && node.getText() == '\u200b';
         }

         return !! ( isReject ^ isWhitespace );
      };
   };

   VED.dom.walker.invisible = function( isReject ) {
      var whitespace = VED.dom.walker.whitespaces();
      return function( node ) {
         var invisible;

         if ( whitespace( node ) )
            invisible = 1;
         else {
            // Visibility should be checked on element.
            if ( node.type == VED.NODE_TEXT )
               node = node.getParent();

         // Nodes that take no spaces in wysiwyg:
         // 1. White-spaces but not including NBSP;
         // 2. Empty inline elements, e.g. <b></b> we're checking here
         // 'offsetHeight' instead of 'offsetWidth' for properly excluding
         // all sorts of empty paragraph, e.g. <br />.
            invisible = !node.$.offsetHeight;
         }

         return !!( isReject ^ invisible );
      };
   };

   VED.dom.walker.nodeType = function( type, isReject ) {
      return function( node ) {
         return !!( isReject ^ ( node.type == type ) );
      };
   };

   VED.dom.walker.bogus = function( isReject ) {
      function nonEmpty( node ) {
         return !isWhitespaces( node ) && !isBookmark( node );
      }

      return function( node ) {
         var isBogus = !VED.env.ie ? node.is && node.is( 'br' ) : node.getText && tailNbspRegex.test( node.getText() );

         if ( isBogus ) {
            var parent = node.getParent(),
               next = node.getNext( nonEmpty );

            isBogus = parent.isBlockBoundary() && ( !next || next.type == VED.NODE_ELEMENT && next.isBlockBoundary() );
         }

         return !!( isReject ^ isBogus );
      };
   };

   var tailNbspRegex = /^[\t\r\n ]*(?:&nbsp;|\xa0)$/,
      isWhitespaces = VED.dom.walker.whitespaces(),
      isBookmark = VED.dom.walker.bookmark(),
      toSkip = function( node ) {
         return isBookmark( node ) || isWhitespaces( node ) || node.type == VED.NODE_ELEMENT && node.getName() in VED.dtd.$inline && !( node.getName() in VED.dtd.$empty );
      };

   VED.dom.element.prototype.getBogus = function() {
      // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
      var tail = this;
      do {
         tail = tail.getPreviousSourceNode();
      }
      while ( toSkip( tail ) )

      if ( tail && ( !VED.env.ie ? tail.is && tail.is( 'br' ) : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) {
         return tail;
      }
      return false;
   };

})();





